/* Copyright (c) 2020 XEPIC Corporation Limited */
/*
 * Copyright (c) 1999-2018 Stephen Williams (steve@icarus.com)
 *
 *    This source code is free software; you can redistribute it
 *    and/or modify it in source code form under the terms of the GNU
 *    General Public License as published by the Free Software
 *    Foundation; either version 2 of the License, or (at your option)
 *    any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
 * USA.
 */

#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "ivl_alloc.h"
#include "sys_priv.h"

#define IS_MCD(mcd) !((mcd) >> 31 & 1)

// Flag to enable better compatibility with other simulators
static unsigned compatible_flag = 0;

static void check_command_line_args(void) {
  struct t_vpi_vlog_info vlog_info;

  vpi_get_vlog_info(&vlog_info);

  for (int idx = 0; idx < vlog_info.argc; idx += 1) {
    if (strcmp(vlog_info.argv[idx], "-compatible") == 0) {
      compatible_flag = 1;
    }
  }
}

/* Printf wrapper to handle both MCD/FD */
static PLI_INT32 my_mcd_printf(PLI_UINT32 mcd, const char *fmt, ...) {
  int r = 0;

  va_list ap;
  va_start(ap, fmt);

  if (IS_MCD(mcd)) {
    r = vpi_mcd_vprintf(mcd, fmt, ap);
  } else {
    FILE *fp = vpi_get_file(mcd);
    if (fp) r = vfprintf(fp, fmt, ap);
  }

  va_end(ap);
  return r;
}

static void my_mcd_rawwrite(PLI_UINT32 mcd, const char *buf, size_t count) {
  if (IS_MCD(mcd)) {
    vpip_mcd_rawwrite(mcd, buf, count);
  } else {
    FILE *fp = vpi_get_file(mcd);
    if (fp) {
      while (count > 0) {
        size_t rc = fwrite(buf, 1, count, fp);
        if (rc == 0) break;
        count -= rc;
        buf += rc;
      }
    }
  }
}

struct timeformat_info_s timeformat_info = {0, 0, 0, 20};

struct strobe_cb_info {
  const char *name;
  char *filename;
  int lineno;
  int default_format;
  vpiHandle scope;
  vpiHandle *items;
  unsigned nitems;
  unsigned fd_mcd;
};

/*
 * The number of decimal digits needed to represent a
 * nr_bits binary number is floor(nr_bits*log_10(2))+1,
 * where log_10(2) = 0.30102999566398....  and I approximate
 * this transcendental number as 146/485, to avoid the vagaries
 * of floating-point.  The smallest nr_bits for which this
 * approximation fails is 2621,
 * 2621*log_10(2)=789.9996, but (2621*146+484)/485=790 (exactly).
 * In cases like this, all that happens is we allocate one
 * unneeded char for the output.  I add a "L" suffix to 146
 * to make sure the computation is done as long ints, otherwise
 * on a 16-bit int machine (allowed by ISO C) we would mangle
 * this computation for bit-length of 224.  I'd like to put
 * in a test for nr_bits < LONG_MAX/146, but don't know how
 * to fail, other than crashing.
 *
 * In an April 2000 thread in comp.unix.programmer, with subject
 * "integer -> string", I <LRDoolittle@lbl.gov> give the 28/93
 * approximation, but overstate its accuracy: that version first
 * fails when the number of bits is 289, not 671.
 *
 * This result does not include space for a trailing '\0', if any.
 */
__inline__ static int calc_dec_size(int nr_bits, int is_signed) {
  int r;
  if (is_signed) --nr_bits;
  r = (nr_bits * 146L + 484) / 485;
  if (is_signed) ++r;
  return r;
}

static int vpi_get_dec_size(vpiHandle item) {
  return calc_dec_size(vpi_get(vpiSize, item), vpi_get(vpiSigned, item) == 1);
}

static void array_from_iterator(struct strobe_cb_info *info, vpiHandle argv) {
  if (argv) {
    vpiHandle item;
    unsigned nitems = 1;
    vpiHandle *items = malloc(sizeof(vpiHandle));
    items[0] = vpi_scan(argv);
    if (items[0] == 0) {
      free(items);
      info->nitems = 0;
      info->items = 0;
      return;
    }

    for (item = vpi_scan(argv); item; item = vpi_scan(argv)) {
      items = realloc(items, (nitems + 1) * sizeof(vpiHandle));
      items[nitems] = item;
      nitems += 1;
    }

    info->nitems = nitems;
    info->items = items;

  } else {
    info->nitems = 0;
    info->items = 0;
  }
}

static int get_default_format(const char *name) {
  int default_format;

  switch (name[strlen(name) - 1]) {
      /*  writE/strobE or monitoR or displaY/fdisplaY or sformaT/sformatF */
    case 'e':
    case 'r':
    case 't':
    case 'f':
    case 'y':
      default_format = vpiDecStrVal;
      break;
    case 'h':
      default_format = vpiHexStrVal;
      break;
    case 'o':
      default_format = vpiOctStrVal;
      break;
    case 'b':
      default_format = vpiBinStrVal;
      break;
    default:
      default_format = -1;
      assert(0);
  }

  return default_format;
}

/* Build the format using the variables that control how the item will
 * be printed. This is used in error messages and directly by the e/f/g
 * format codes (minus the enclosing <>). The user needs to free the
 * returned string. */
static char *format_as_string(int ljust, int plus, int ld_zero, int width,
                              int prec, char fmt) {
  char buf[256];
  unsigned int size = 0;

  /* Do not remove/change the "<" without also changing the e/f/g format
   * code below! */
  buf[size++] = '<';
  buf[size++] = '%';
  if (ljust == 1) buf[size++] = '-';
  if (plus == 1) buf[size++] = '+';
  if (ld_zero == 1) buf[size++] = '0';
  if (width != -1) size += sprintf(&buf[size], "%d", width);
  if (prec != -1) size += sprintf(&buf[size], ".%d", prec);
  if (fmt) buf[size++] = fmt;
  /* The same goes here ">"! */
  buf[size++] = '>';
  buf[size] = '\0';
  return strdup(buf);
}

static void get_time(char *rtn, const char *value, int prec,
                     PLI_INT32 time_units) {
  int shift = time_units - timeformat_info.units;

  /* Strip any leading zeros, but leave a single zero. */
  while (value[0] == '0' && value[1] != '\0') value += 1;
  /* We need to scale the number up. */
  if (shift >= 0) {
    strcpy(rtn, value);
    /* Shift only non-zero values. */
    while (shift > 0 && value[0] != '0') {
      strcat(rtn, "0");
      shift -= 1;
    }
    if (prec > 0) strcat(rtn, ".");
    while (prec > 0) {
      strcat(rtn, "0");
      prec -= 1;
    }

    /* We need to scale the number down. */
  } else {
    int len = strlen(value);
    int head = len + shift;
    int tail;
    /* We have digits to the left of the decimal point. */
    if (head > 0) {
      strncpy(rtn, value, head);
      *(rtn + head) = '\0';
      if (prec > 0) {
        strcat(rtn, ".");
        strncat(rtn, &value[head], prec);
        tail = prec + shift;
        while (tail > 0) {
          strcat(rtn, "0");
          tail -= 1;
        }
      }
      /* All digits are to the right of the decimal point. */
    } else {
      strcpy(rtn, "0");
      if (prec > 0) strcat(rtn, ".");
      /* Add leading zeros as needed. */
      head = -head;
      if (head > prec) head = prec;
      while (head > 0) {
        strcat(rtn, "0");
        head -= 1;
      }
      /* Add digits from the value if they fit. */
      tail = prec + len + shift;
      if (tail > 0) {
        strncat(rtn, value, tail);
        /* Add trailing zeros to fill out the precision. */
        tail = prec + shift + 1 - len;
        while (tail > 0) {
          strcat(rtn, "0");
          tail -= 1;
        }
      }
    }
  }

  strcat(rtn, timeformat_info.suff);
}

static void get_time_real(char *rtn, double value, int prec,
                          PLI_INT32 time_units) {
  /* Scale the value from its time units to the format time units. */
  if (time_units >= timeformat_info.units) {
    value *= pow(10.0, time_units - timeformat_info.units);
  } else {
    value /= pow(10.0, timeformat_info.units - time_units);
  }
  sprintf(rtn, "%0.*f%s", prec, value, timeformat_info.suff);
}

static unsigned int get_format_char(char **rtn, int ljust, int plus,
                                    int ld_zero, int width, int prec, char fmt,
                                    const struct strobe_cb_info *info,
                                    unsigned int *idx) {
  s_vpi_value value;
  char *result, *fmtb;
  unsigned int size;
  unsigned int ini_size = 512; /* The initial size of the buffer. */

  /* Make sure the width fits in the initial buffer. */
  assert(width >= -1);
  if ((unsigned int)(width + 1) > ini_size) ini_size = width + 1;

  /* The default return value is the full format. */
  result = malloc(ini_size * sizeof(char));
  fmtb = format_as_string(ljust, plus, ld_zero, width, prec, fmt);
  strcpy(result, fmtb);
  size = strlen(result) + 1; /* fallback value if errors */
  switch (fmt) {
    case '%':
    case '\0':
      if (ljust != 0 || plus != 0 || ld_zero != 0 || width != -1 ||
          prec != -1) {
        vpi_printf("WARNING: %s:%d: invalid format %s%s.\n", info->filename,
                   info->lineno, info->name, fmtb);
      }
      strcpy(result, "%");
      size = strlen(result) + 1;
      break;

    case 'b':
    case 'B':
    case 'o':
    case 'O':
    case 'h':
    case 'H':
    case 'x':
    case 'X':
      *idx += 1;
      if (plus != 0 || prec != -1) {
        vpi_printf("WARNING: %s:%d: invalid format %s%s.\n", info->filename,
                   info->lineno, info->name, fmtb);
      }
      if (*idx >= info->nitems) {
        vpi_printf("WARNING: %s:%d: missing argument for %s%s.\n",
                   info->filename, info->lineno, info->name, fmtb);
      } else {
        switch (fmt) {
          case 'b':
          case 'B':
            value.format = vpiBinStrVal;
            break;
          case 'o':
          case 'O':
            value.format = vpiOctStrVal;
            break;
          case 'h':
          case 'H':
          case 'x':
          case 'X':
            value.format = vpiHexStrVal;
            break;
        }
        vpi_get_value(info->items[*idx], &value);
        if (value.format == vpiSuppressVal) {
          vpi_printf("WARNING: %s:%d: incompatible value for %s%s.\n",
                     info->filename, info->lineno, info->name, fmtb);
        } else {
          unsigned swidth = strlen(value.value.str), free_flag = 0;
          ;
          char *cp = value.value.str;

          if (ld_zero == 1) {
            /* Strip the leading zeros if a width is not given. */
            if (width == -1)
              while (*cp == '0' && *(cp + 1) != '\0') cp++;
            /* Pad with leading zeros. */
            else if (ljust == 0 && (signed)swidth < width) {
              unsigned pad = (unsigned)width - swidth;
              cp = malloc((width + 1) * sizeof(char));
              memset(cp, '0', pad);
              strcpy(cp + pad, value.value.str);
              free_flag = 1;
              /* For a left aligned value also strip the leading zeros. */
            } else if (ljust != 0)
              while (*cp == '0' && *(cp + 1) != '\0') cp++;
          }

          /* If a width was not given, use a width of zero. */
          if (width == -1) width = 0;

          /* If the default buffer is too small, make it big enough. */
          size = strlen(cp) + 1;
          if ((signed)size < (width + 1)) size = width + 1;
          if (size > ini_size) result = realloc(result, size * sizeof(char));

          if (ljust == 0)
            sprintf(result, "%*s", width, cp);
          else
            sprintf(result, "%-*s", width, cp);
          if (free_flag) free(cp);
          size = strlen(result) + 1;
        }
      }
      break;

    case 'c':
    case 'C':
      *idx += 1;
      if (plus != 0 || prec != -1) {
        vpi_printf("WARNING: %s:%d: invalid format %s%s.\n", info->filename,
                   info->lineno, info->name, fmtb);
      }
      if (*idx >= info->nitems) {
        vpi_printf("WARNING: %s:%d: missing argument for %s%s.\n",
                   info->filename, info->lineno, info->name, fmtb);
      } else {
        value.format = vpiIntVal;
        vpi_get_value(info->items[*idx], &value);
        if (value.format == vpiSuppressVal) {
          vpi_printf("WARNING: %s:%d: incompatible value for %s%s.\n",
                     info->filename, info->lineno, info->name, fmtb);
        } else {
          char ch = value.value.integer;

          /* If the width is less than one then use a width of one. */
          if (width < 1) width = 1;
          size = width + 1;
          assert(size <= ini_size);

          if (ljust == 0) {
            if (width > 1) {
              memset(result, (ld_zero == 1 ? '0' : ' '), width - 1);
            }
            result[width - 1] = ch;
          } else {
            result[0] = ch;
            if (width > 1) {
              memset(result + 1, ' ', width - 1);
            }
          }
          result[width] = '\0';
        }
      }
      break;

    case 'd':
    case 'D':
      *idx += 1;
      if (prec != -1) {
        vpi_printf("WARNING: %s:%d: invalid format %s%s.\n", info->filename,
                   info->lineno, info->name, fmtb);
      }
      if (*idx >= info->nitems) {
        vpi_printf("WARNING: %s:%d: missing argument for %s%s.\n",
                   info->filename, info->lineno, info->name, fmtb);
      } else {
        value.format = vpiDecStrVal;
        vpi_get_value(info->items[*idx], &value);
        if (value.format == vpiSuppressVal) {
          vpi_printf("WARNING: %s:%d: incompatible value for %s%s.\n",
                     info->filename, info->lineno, info->name, fmtb);
        } else {
          unsigned pad = 0;
          unsigned swidth = strlen(value.value.str) +
                            (value.value.str[0] == '-' ? 0 : (unsigned)plus);
          char *tbuf, *cpb, *cp = value.value.str;

          /* Allocate storage and calculate the pad if needed. */
          if (ljust == 0 && ld_zero == 1 && (signed)swidth < width) {
            tbuf = malloc((width + 1) * sizeof(char));
            pad = (unsigned)width - swidth;
          } else {
            tbuf = malloc((swidth + 1) * sizeof(char));
          }
          cpb = tbuf;

          /* Insert the sign if needed. */
          if (plus == 1 && *cp != '-') {
            *cpb = '+';
            cpb += 1;
          } else if (*cp == '-') {
            *cpb = '-';
            cpb += 1;
            cp += 1;
          }

          /* Now add padding if it is needed and then add the value. */
          memset(cpb, '0', pad);
          strcpy(cpb + pad, cp);

          /* If a width was not given, use the default, unless we have a
           * leading zero (width of zero). Because the width of a real in
           * Icarus is 1 the string length will set the width of a real
           * displayed using %d. */
          if (width == -1) {
            width = (ld_zero == 1) ? 0 : vpi_get_dec_size(info->items[*idx]);
          }

          /* If the default buffer is too small make it big enough. */
          size = strlen(tbuf) + 1;
          if ((signed)size < (width + 1)) size = width + 1;
          if (size > ini_size) result = realloc(result, size * sizeof(char));

          if (ljust == 0)
            sprintf(result, "%*s", width, tbuf);
          else
            sprintf(result, "%-*s", width, tbuf);
          free(tbuf);
          size = strlen(result) + 1;
        }
      }
      break;

    case 'e':
    case 'E':
    case 'f':
    case 'F':
    case 'g':
    case 'G':
      *idx += 1;
      if (*idx >= info->nitems) {
        vpi_printf("WARNING: %s:%d: missing argument for %s%s.\n",
                   info->filename, info->lineno, info->name, fmtb);
      } else {
        value.format = vpiRealVal;
        vpi_get_value(info->items[*idx], &value);
        if (value.format == vpiSuppressVal) {
          vpi_printf("WARNING: %s:%d: incompatible value for %s%s.\n",
                     info->filename, info->lineno, info->name, fmtb);
        } else {
          char *cp = fmtb;

          if (fmt == 'F') {
            while (*cp != 'F') cp++;
            *cp = 'f';
          }
          while (*cp != '>') cp++;
          *cp = '\0';

          /* If the default buffer is too small make it big enough.
           *
           * This should always give enough space. The maximum double
           * is approximately 1.8*10^308 this means we could need 310
           * characters plus the precision. We'll use 320 to give some
           * extra buffer space. The initial buffers size should work
           * for most cases, but to be safe we add the precision to
           * the maximum size (think %6.300f when passed 1.2*10^308). */
          size = width + 1;
          if (size < 320) size = 320;
          size += prec;
          if (size > ini_size) result = realloc(result, size * sizeof(char));
#if !defined(__GNUC__)
          if (isnan(value.value.real))
            sprintf(result, "%s", "nan");
          else
            sprintf(result, fmtb + 1, value.value.real);
#else
          sprintf(result, fmtb + 1, value.value.real);
#endif
          size = strlen(result) + 1;
        }
      }
      break;

    /* This Verilog format specifier is not currently supported!
     * vpiCell and vpiLibrary need to be implemented first. */
    case 'l':
    case 'L':
      vpi_printf("WARNING: %s:%d: %%%c currently unsupported %s%s.\n",
                 info->filename, info->lineno, fmt, info->name, fmtb);
      break;

    case 'm':
    case 'M':
      if (plus != 0 || prec != -1) {
        vpi_printf("WARNING: %s:%d: invalid format %s%s.\n", info->filename,
                   info->lineno, info->name, fmtb);
      }
      /* If a width was not given, use a width of zero. */
      if (width == -1) width = 0;

      {
        char *cp = vpi_get_str(vpiFullName, info->scope);
        /* If the default buffer is too small, make it big enough. */
        size = strlen(cp) + 1;
        if ((signed)size < (width + 1)) size = width + 1;
        if (size > ini_size) result = realloc(result, size * sizeof(char));

        if (ljust == 0)
          sprintf(result, "%*s", width, cp);
        else
          sprintf(result, "%-*s", width, cp);
      }
      size = strlen(result) + 1;
      break;

    case 's':
    case 'S':
      /* Strings are not numeric and are not zero filled, so %08s => %8s. */
      *idx += 1;
      if (plus != 0 || prec != -1) {
        vpi_printf("WARNING: %s:%d: invalid format %s%s.\n", info->filename,
                   info->lineno, info->name, fmtb);
      }
      if (*idx >= info->nitems) {
        vpi_printf("WARNING: %s:%d: missing argument for %s%s.\n",
                   info->filename, info->lineno, info->name, fmtb);
      } else {
        value.format = vpiStringVal;
        vpi_get_value(info->items[*idx], &value);
        if (value.format == vpiSuppressVal) {
          vpi_printf("WARNING: %s:%d: incompatible value for %s%s.\n",
                     info->filename, info->lineno, info->name, fmtb);
        } else {
          if (width == -1) {
            /* If all we have is a leading zero then we want a zero width. */
            if (ld_zero == 1) width = 0;
            /* Otherwise if a width was not given, use the value width. */
            else
              width = (vpi_get(vpiSize, info->items[*idx]) + 7) / 8;
          }
          /* If the default buffer is too small make it big enough. */
          size = strlen(value.value.str) + 1;
          if ((signed)size < (width + 1)) size = width + 1;
          if (size > ini_size) result = realloc(result, size * sizeof(char));
          if (ljust == 0)
            sprintf(result, "%*s", width, value.value.str);
          else
            sprintf(result, "%-*s", width, value.value.str);
          size = strlen(result) + 1;
        }
      }
      break;

    case 't':
    case 'T':
      *idx += 1;
      if (*idx >= info->nitems) {
        vpi_printf("WARNING: %s:%d: missing argument for %s%s.\n",
                   info->filename, info->lineno, info->name, fmtb);
      } else {
        PLI_INT32 type;

        /* Get the argument type and value. */
        type = vpi_get(vpiType, info->items[*idx]);
        if (((type == vpiConstant || type == vpiParameter) &&
             vpi_get(vpiConstType, info->items[*idx]) == vpiRealConst) ||
            type == vpiRealVar ||
            (type == vpiSysFuncCall &&
             vpi_get(vpiFuncType, info->items[*idx]) == vpiRealFunc)) {
          value.format = vpiRealVal;
        } else {
          value.format = vpiDecStrVal;
        }
        vpi_get_value(info->items[*idx], &value);
        if (value.format == vpiSuppressVal) {
          vpi_printf("WARNING: %s:%d: incompatible value for %s%s.\n",
                     info->filename, info->lineno, info->name, fmtb);
        } else {
          char *tbuf, *prev_suff = 0;
          PLI_INT32 time_units = vpi_get(vpiTimeUnit, info->scope);

          if (plus != 0) {
            /* Icarus-specific extension to print out time units.
             * It is needed by vhdlpp to correctly implement time'image(). */
            PLI_INT32 time_prec = vpi_get(vpiTimePrecision, info->scope);

            /* We are about to override the suffix string set with
             * $timeformat(), therefore we need to restore it after the call. */
            prev_suff = timeformat_info.suff;

            if (time_units < -12)
              timeformat_info.suff = strdup(" fs");
            else if (time_units < -9)
              timeformat_info.suff = strdup(" ps");
            else if (time_units < -6)
              timeformat_info.suff = strdup(" ns");
            else if (time_units < -3)
              timeformat_info.suff = strdup(" us");
            else if (time_units < 0)
              timeformat_info.suff = strdup(" ms");
            else
              timeformat_info.suff = strdup(" s");

            /* Adjust shift for get_time(), so the number indeed matches the
             * unit */
            time_units += (3 + (time_units % 3)) % 3 + (time_prec - time_units);
          }

          unsigned swidth, free_flag = 0;
          unsigned suff_len = strlen(timeformat_info.suff);
          char *cp;

          /* The 512 (513-1 for EOL) is more than enough for any double
           * value (309 digits plus a decimal point maximum). Because of
           * scaling this could be larger. For decimal values you can
           * have an arbitrary value so you can overflow the buffer, but
           * for now we will assume the user will use this as intended
           * (pass a time variable or the result of a time function). */
          tbuf = malloc((513 + suff_len) * sizeof(char));
          if (prec == -1) prec = timeformat_info.prec;
          if (value.format == vpiRealVal) {
            get_time_real(tbuf, value.value.real, prec, time_units);
          } else {
            get_time(tbuf, value.value.str, prec, time_units);
          }
          cp = tbuf;
          swidth = strlen(tbuf);

          if (plus != 0) {
            /* Restore the previous suffix string, overridden by
             * a vhdlpp-specific $sformatf() call. */
            free(timeformat_info.suff);
            timeformat_info.suff = prev_suff;
          }

          if (ld_zero == 1) {
            /* No leading zeros are created by this conversion so just make
             * the width 0 for this case. */
            if (width == -1) width = 0;
            /* Pad with leading zeros. */
            else if (ljust == 0 && (signed)swidth < width) {
              unsigned pad = (unsigned)width - swidth;
              cp = malloc((width + 1) * sizeof(char));
              memset(cp, '0', pad);
              strcpy(cp + pad, tbuf);
              free_flag = 1;
            }
          }
          if (width == -1) width = timeformat_info.width;

          /* If the default buffer is too small make it big enough. */
          size = strlen(tbuf) + 1;
          if ((signed)size < (width + 1)) size = width + 1;
          if (size > ini_size) result = realloc(result, size * sizeof(char));

          if (ljust == 0)
            sprintf(result, "%*s", width, cp);
          else
            sprintf(result, "%-*s", width, cp);
          if (free_flag) free(cp);
          free(tbuf);
          size = strlen(result) + 1;
        }
      }
      break;

    case 'u':
    case 'U':
      *idx += 1;
      if (ljust != 0 || plus != 0 || ld_zero != 0 || width != -1 ||
          prec != -1) {
        vpi_printf("WARNING: %s:%d: invalid format %s%s.\n", info->filename,
                   info->lineno, info->name, fmtb);
      }
      if (*idx >= info->nitems) {
        vpi_printf("WARNING: %s:%d: missing argument for %s%s.\n",
                   info->filename, info->lineno, info->name, fmtb);
      } else {
        value.format = vpiVectorVal;
        vpi_get_value(info->items[*idx], &value);
        if (value.format == vpiSuppressVal) {
          vpi_printf("WARNING: %s:%d: incompatible value for %s%s.\n",
                     info->filename, info->lineno, info->name, fmtb);
        } else {
          PLI_INT32 veclen, word, byte;
          char *cp;

          veclen = (vpi_get(vpiSize, info->items[*idx]) + 31) / 32;
          size = veclen * 4 + 1;
          /* If the default buffer is too small, make it big enough. */
          if (size > ini_size) result = realloc(result, size * sizeof(char));
          cp = result;
          for (word = 0; word < veclen; word += 1) {
            PLI_INT32 bits =
                value.value.vector[word].aval & ~value.value.vector[word].bval;
#ifdef WORDS_BIGENDIAN
            for (byte = 3; byte >= 0; byte -= 1) {
#else
            for (byte = 0; byte <= 3; byte += 1) {
#endif
              *cp = (bits >> byte * 8) & 0xff;
              cp += 1;
            }
          }
          *cp = '\0';
        }
      }
      /* size is defined above! We can't use strlen here since this can
       * be a binary string (can contain NULLs). */
      break;

    case 'v':
    case 'V':
      *idx += 1;
      if (plus != 0 || prec != -1) {
        vpi_printf("WARNING: %s:%d: invalid format %s%s.\n", info->filename,
                   info->lineno, info->name, fmtb);
      }
      if (*idx >= info->nitems) {
        vpi_printf("WARNING: %s:%d: missing argument for %s%s.\n",
                   info->filename, info->lineno, info->name, fmtb);
      } else {
        value.format = vpiStrengthVal;
        vpi_get_value(info->items[*idx], &value);
        if (value.format == vpiSuppressVal) {
          vpi_printf("WARNING: %s:%d: incompatible value for %s%s.\n",
                     info->filename, info->lineno, info->name, fmtb);
        } else {
          char tbuf[4], *rbuf;
          PLI_INT32 nbits;
          int bit;

          /* If a width was not given use a width of zero. */
          if (width == -1) width = 0;
          nbits = vpi_get(vpiSize, info->items[*idx]);
          /* This is 4 chars for all but the last bit (strength + "_")
           * which only needs three chars (strength), but then you need
           * space for the EOS '\0', so it is just number of bits * 4. */
          size = nbits * 4;
          rbuf = malloc(size * sizeof(char));
          if ((signed)size < (width + 1)) size = width + 1;
          if (size > ini_size) result = realloc(result, size * sizeof(char));
          strcpy(rbuf, "");
          for (bit = nbits - 1; bit >= 0; bit -= 1) {
            vpip_format_strength(tbuf, &value, bit);
            strcat(rbuf, tbuf);
            if (bit > 0) strcat(rbuf, "_");
          }
          if (ljust == 0)
            sprintf(result, "%*s", width, rbuf);
          else
            sprintf(result, "%-*s", width, rbuf);
          free(rbuf);
          size = strlen(result) + 1;
        }
      }
      break;

    case 'z':
    case 'Z':
      *idx += 1;
      size = strlen(result) + 1; /* fallback value if errors */
      if (ljust != 0 || plus != 0 || ld_zero != 0 || width != -1 ||
          prec != -1) {
        vpi_printf("WARNING: %s:%d: invalid format %s%s.\n", info->filename,
                   info->lineno, info->name, fmtb);
      }
      if (*idx >= info->nitems) {
        vpi_printf("WARNING: %s:%d: missing argument for %s%s.\n",
                   info->filename, info->lineno, info->name, fmtb);
      } else {
        value.format = vpiVectorVal;
        vpi_get_value(info->items[*idx], &value);
        if (value.format == vpiSuppressVal) {
          vpi_printf("WARNING: %s:%d: incompatible value for %s%s.\n",
                     info->filename, info->lineno, info->name, fmtb);
        } else {
          PLI_INT32 veclen, word, elem, bits, byte;
          char *cp;

          veclen = (vpi_get(vpiSize, info->items[*idx]) + 31) / 32;
          size = 2 * veclen * 4 + 1;
          /* If the default buffer is too small, make it big enough. */
          if (size > ini_size) result = realloc(result, size * sizeof(char));
          cp = result;
          for (word = 0; word < veclen; word += 1) {
            /* Write the aval followed by the bval in endian order. */
            for (elem = 0; elem < 2; elem += 1) {
              bits = *(&value.value.vector[word].aval + elem);
#ifdef WORDS_BIGENDIAN
              for (byte = 3; byte >= 0; byte -= 1) {
#else
              for (byte = 0; byte <= 3; byte += 1) {
#endif
                *cp = (bits >> byte * 8) & 0xff;
                cp += 1;
              }
            }
          }
          *cp = '\0';
        }
      }
      /* size is defined above! We can't use strlen here since this can
       * be a binary string (can contain NULLs). */
      break;

    default:
      vpi_printf("WARNING: %s:%d: unknown format %s%s.\n", info->filename,
                 info->lineno, info->name, fmtb);
      size = strlen(result) + 1;
      break;
  }
  free(fmtb);
  /* We can't use strdup here since %u and %z can insert NULL
   * characters into the stream. */
  *rtn = malloc(size * sizeof(char));
  memcpy(*rtn, result, size);
  free(result);
  return size - 1;
}

/* We can't use the normal str functions on the return value since
 * %u and %z can insert NULL characters into the stream. */
static unsigned int get_format(char **rtn, char *fmt,
                               const struct strobe_cb_info *info,
                               unsigned int *idx) {
  char *cp = fmt;
  unsigned int size;

  *rtn = strdup("");
  size = 1;
  while (*cp) {
    size_t cnt = strcspn(cp, "%");

    if (cnt > 0) {
      *rtn = realloc(*rtn, (size + cnt) * sizeof(char));
      memcpy(*rtn + size - 1, cp, cnt);
      size += cnt;
      cp += cnt;
    } else {
      int ljust = 0, plus = 0, ld_zero = 0, width = -1, prec = -1;
      char *result;

      cp += 1;
      while ((*cp == '-') || (*cp == '+')) {
        if (*cp == '-')
          ljust = 1;
        else
          plus = 1;
        cp += 1;
      }
      if (*cp == '0') {
        ld_zero = 1;
        cp += 1;
      }
      if (isdigit((int)*cp)) width = strtoul(cp, &cp, 10);
      if (*cp == '.') {
        cp += 1;
        prec = strtoul(cp, &cp, 10);
      }
      cnt = get_format_char(&result, ljust, plus, ld_zero, width, prec, *cp,
                            info, idx);
      *rtn = realloc(*rtn, (size + cnt) * sizeof(char));
      memcpy(*rtn + size - 1, result, cnt);
      free(result);
      size += cnt;
      if (*cp) cp += 1;
    }
  }
  *(*rtn + size - 1) = '\0';
  return size - 1;
}

static unsigned int get_numeric(char **rtn, const struct strobe_cb_info *info,
                                vpiHandle item) {
  int size, min;
  s_vpi_value val;

  val.format = info->default_format;
  vpi_get_value(item, &val);

  switch (info->default_format) {
    case vpiDecStrVal:
      size = vpi_get_dec_size(item);
      /* -1 can be represented as a one bit signed value. This returns
       * a size of 1 which is too small for the -1 string value so make
       * the string width the minimum display width. */
      min = strlen(val.value.str);
      if (size < min) size = min;
      *rtn = malloc((size + 1) * sizeof(char));
      sprintf(*rtn, "%*s", size, val.value.str);
      break;
    default:
      *rtn = strdup(val.value.str);
  }

  return strlen(*rtn);
}

/* In many places we can't use the normal str functions since %u and %z
 * can insert NULL characters into the stream. */
static char *get_display(unsigned int *rtnsz,
                         const struct strobe_cb_info *info) {
  char *result, *fmt, *rtn, *func_name;
  const char *cresult;
  s_vpi_value value;
  unsigned int idx, size, width;
  char buf[256];

  rtn = strdup("");
  size = 1;
  for (idx = 0; idx < info->nitems; idx += 1) {
    vpiHandle item = info->items[idx];

    switch (vpi_get(vpiType, item)) {
      case vpiConstant:
      case vpiParameter:
        if (vpi_get(vpiConstType, item) == vpiStringConst) {
          value.format = vpiStringVal;
          vpi_get_value(item, &value);
          fmt = strdup(value.value.str);
          width = get_format(&result, fmt, info, &idx);
          free(fmt);
        } else if (vpi_get(vpiConstType, item) == vpiRealConst) {
          value.format = vpiRealVal;
          vpi_get_value(item, &value);
#if !defined(__GNUC__)
          if (compatible_flag)
            sprintf(buf, "%g", value.value.real);
          else {
            if (value.value.real == 0.0 || value.value.real == -0.0)
              sprintf(buf, "%.05f", value.value.real);
            else
              sprintf(buf, "%#g", value.value.real);
          }
#else
          sprintf(buf, compatible_flag ? "%g" : "%#g", value.value.real);
#endif
          result = strdup(buf);
          width = strlen(result);
        } else {
          width = get_numeric(&result, info, item);
        }
        rtn = realloc(rtn, (size + width) * sizeof(char));
        memcpy(rtn + size - 1, result, width);
        free(result);
        break;

      case vpiNet:
      case vpiReg:
      case vpiBitVar:
      case vpiByteVar:
      case vpiShortIntVar:
      case vpiIntVar:
      case vpiLongIntVar:
      case vpiIntegerVar:
      case vpiMemoryWord:
      case vpiPartSelect:
        width = get_numeric(&result, info, item);
        rtn = realloc(rtn, (size + width) * sizeof(char));
        memcpy(rtn + size - 1, result, width);
        free(result);
        break;

      /* It appears that this is not currently used! A time variable is
         passed as an integer and processed above. Hence this code has
         only been visually checked. */
      case vpiTimeVar:
        value.format = vpiDecStrVal;
        vpi_get_value(item, &value);
        get_time(buf, value.value.str, timeformat_info.prec,
                 vpi_get(vpiTimeUnit, info->scope));
        width = strlen(buf);
        if (width < timeformat_info.width) width = timeformat_info.width;
        rtn = realloc(rtn, (size + width) * sizeof(char));
        sprintf(rtn + size - 1, "%*s", width, buf);
        break;

      /* Realtime variables are also processed here. */
      case vpiRealVar:
        value.format = vpiRealVal;
        vpi_get_value(item, &value);
#if !defined(__GNUC__)
        if (compatible_flag)
          sprintf(buf, "%g", value.value.real);
        else {
          if (value.value.real == 0.0 || value.value.real == -0.0)
            sprintf(buf, "%.05f", value.value.real);
          else
            sprintf(buf, "%#g", value.value.real);
        }
#else
        sprintf(buf, compatible_flag ? "%g" : "%#g", value.value.real);
#endif
        width = strlen(buf);
        rtn = realloc(rtn, (size + width) * sizeof(char));
        memcpy(rtn + size - 1, buf, width);
        break;

        /* Process string variables like string constants: interpret
           the contained strings like format strings. */
      case vpiStringVar:
        value.format = vpiStringVal;
        vpi_get_value(item, &value);
        fmt = strdup(value.value.str);
        width = get_format(&result, fmt, info, &idx);
        free(fmt);
        rtn = realloc(rtn, (size + width) * sizeof(char));
        memcpy(rtn + size - 1, result, width);
        free(result);
        break;

      case vpiSysFuncCall:
        func_name = vpi_get_str(vpiName, item);
        if (strcmp(func_name, "$time") == 0) {
          value.format = vpiDecStrVal;
          vpi_get_value(item, &value);
          width = strlen(value.value.str);
          if (width < 20) width = 20;
          rtn = realloc(rtn, (size + width) * sizeof(char));
          sprintf(rtn + size - 1, "%*s", width, value.value.str);

        } else if (strcmp(func_name, "$stime") == 0) {
          value.format = vpiDecStrVal;
          vpi_get_value(item, &value);
          width = strlen(value.value.str);
          if (width < 10) width = 10;
          rtn = realloc(rtn, (size + width) * sizeof(char));
          sprintf(rtn + size - 1, "%*s", width, value.value.str);

        } else if (strcmp(func_name, "$simtime") == 0) {
          value.format = vpiDecStrVal;
          vpi_get_value(item, &value);
          width = strlen(value.value.str);
          if (width < 20) width = 20;
          rtn = realloc(rtn, (size + width) * sizeof(char));
          sprintf(rtn + size - 1, "%*s", width, value.value.str);

        } else if (strcmp(func_name, "$realtime") == 0) {
          /* Use the local scope precision. */
          int use_prec = vpi_get(vpiTimeUnit, info->scope) -
                         vpi_get(vpiTimePrecision, info->scope);
          assert(use_prec >= 0);
          value.format = vpiRealVal;
          vpi_get_value(item, &value);
          sprintf(buf, "%.*f", use_prec, value.value.real);
          width = strlen(buf);
          rtn = realloc(rtn, (size + width) * sizeof(char));
          sprintf(rtn + size - 1, "%*s", width, buf);

        } else {
          vpi_printf("WARNING: %s:%d: %s does not support %s as an argument!\n",
                     info->filename, info->lineno, info->name, func_name);
          strcpy(buf, "<?>");
          width = strlen(buf);
          rtn = realloc(rtn, (size + width) * sizeof(char));
          memcpy(rtn + size - 1, buf, width);
        }
        break;

      default:
        vpi_printf("WARNING: %s:%d: unknown argument type (%s) given to %s!\n",
                   info->filename, info->lineno, vpi_get_str(vpiType, item),
                   info->name);
        cresult = "<?>";
        width = strlen(cresult);
        rtn = realloc(rtn, (size + width) * sizeof(char));
        memcpy(rtn + size - 1, cresult, width);
        break;
    }
    size += width;
  }
  rtn[size - 1] = '\0';
  *rtnsz = size - 1;
  return rtn;
}

#ifdef BR916_STOPGAP_FIX
static char br916_hint_issued = 0;
#endif

static int sys_check_args(vpiHandle callh, vpiHandle argv,
                          const PLI_BYTE8 *name, int no_auto, int is_monitor) {
  vpiHandle arg;
  int ret = 0;

  /* If there are no arguments, just return. */
  if (argv == 0) return ret;

  for (arg = vpi_scan(argv); arg; arg = vpi_scan(argv)) {
    if (no_auto && vpi_get(vpiAutomatic, arg)) {
      vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
                 (int)vpi_get(vpiLineNo, callh));
      vpi_printf("%s argument \"%s\" is an automatic variable.\n", name,
                 vpi_get_str(vpiName, arg));
      ret = 1;
    }

    switch (vpi_get(vpiType, arg)) {
      case vpiMemoryWord:
      case vpiPartSelect:
        if (is_monitor && vpi_get(vpiConstantSelect, arg) == 0) {
          vpi_printf("SORRY: %s:%d: ", vpi_get_str(vpiFile, callh),
                     (int)vpi_get(vpiLineNo, callh));
          vpi_printf("%s must have a constant %s select.\n", name,
                     vpi_get_str(vpiType, arg));
          ret = 1;
        }
        // fallthrough
      case vpiConstant:
      case vpiParameter:
      case vpiNet:
      case vpiReg:
      case vpiIntegerVar:
      case vpiBitVar:
      case vpiByteVar:
      case vpiShortIntVar:
      case vpiIntVar:
      case vpiLongIntVar:
      case vpiTimeVar:
      case vpiRealVar:
      case vpiStringVar:
#ifdef BR916_STOPGAP_FIX
        // no_auto implies either $strobe or $monitor
        if (no_auto) {
          switch (vpi_get(_vpiFromThr, arg)) {
            case _vpiVThr:
            case _vpiWord:
            case _vpiString:
              vpi_printf("SORRY: %s:%d: ", vpi_get_str(vpiFile, callh),
                         (int)vpi_get(vpiLineNo, callh));
              vpi_printf(
                  "currently only simple signals or constant "
                  "expressions may be passed to %s.\n",
                  name);
              if (!br916_hint_issued) {
                vpi_printf(
                    "NOTE: You can work around this by "
                    "assigning the desired expression "
                    "to an\n"
                    "      intermediate net (using a "
                    "continuous assignment) and passing "
                    "that net\n"
                    "      to %s.\n",
                    name);
                br916_hint_issued = 1;
              }
              ret = 1;
            default:
              break;
          }
        }
#endif
      case vpiSysFuncCall:
        break;

      default:
        vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
                   (int)vpi_get(vpiLineNo, callh));
        vpi_printf("%s does not support argument type (%s).\n", name,
                   vpi_get_str(vpiType, arg));
        ret = 1;
        break;
    }
  }

  return ret;
}

/* Common compiletf routine. */
static PLI_INT32 sys_common_compiletf(ICARUS_VPI_CONST PLI_BYTE8 *name,
                                      int no_auto, int is_monitor) {
  vpiHandle callh, argv;

  callh = vpi_handle(vpiSysTfCall, 0);
  argv = vpi_iterate(vpiArgument, callh);

  if (name[1] == 'f') {
    /* Check that there is a fd/mcd and that it is numeric. */
    if (argv == 0) {
      vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
                 (int)vpi_get(vpiLineNo, callh));
      vpi_printf("%s requires at least a file descriptor/MCD.\n", name);
      vpi_control(vpiFinish, 1);
      return 0;
    }

    if (!is_numeric_obj(vpi_scan(argv))) {
      vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
                 (int)vpi_get(vpiLineNo, callh));
      vpi_printf("%s's file descriptor/MCD must be numeric.\n", name);
      vpi_control(vpiFinish, 1);
    }
  }

  if (sys_check_args(callh, argv, name, no_auto, is_monitor)) {
    vpi_control(vpiFinish, 1);
  }
  return 0;
}

/* Check the $display, $write, $fdisplay and $fwrite based tasks. */
static PLI_INT32 sys_display_compiletf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  /* These tasks can have automatic variables and are not monitor. */
  return sys_common_compiletf(name, 0, 0);
}

/* This implements the $sformatf, $display/$fdisplay
 * and the $write/$fwrite based tasks. */
static PLI_INT32 sys_display_calltf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh, argv, scope;
  struct strobe_cb_info info;
  char *result;
  unsigned int size;
  PLI_UINT32 fd_mcd;
  s_vpi_value val;

  callh = vpi_handle(vpiSysTfCall, 0);
  argv = vpi_iterate(vpiArgument, callh);

  /* Get the file/MC descriptor and verify it is valid. */
  if (name[1] == 'f') {
    errno = 0;
    vpiHandle arg = vpi_scan(argv);
    val.format = vpiIntVal;
    vpi_get_value(arg, &val);
    fd_mcd = val.value.integer;

    /* If the MCD is zero we have nothing to do so just return. */
    if (fd_mcd == 0) {
      vpi_free_object(argv);
      return 0;
    }

    if ((!IS_MCD(fd_mcd) && vpi_get_file(fd_mcd) == NULL) ||
        (IS_MCD(fd_mcd) && my_mcd_printf(fd_mcd, "") == EOF)) {
      vpi_printf("WARNING: %s:%d: ", vpi_get_str(vpiFile, callh),
                 (int)vpi_get(vpiLineNo, callh));
      vpi_printf(
          "invalid file descriptor/MCD (0x%x) given "
          "to %s.\n",
          (unsigned int)fd_mcd, name);
      errno = EBADF;
      vpi_free_object(argv);
      return 0;
    }
  } else if (strncmp(name, "$sformatf", 9) == 0) {
    /* return as a string */
    fd_mcd = 0;
  } else {
    /* stdout */
    fd_mcd = 1;
  }

  scope = vpi_handle(vpiScope, callh);
  assert(scope);
  /* We could use vpi_get_str(vpiName, callh) to get the task name,
   * but name is already defined. */
  info.name = name;
  info.filename = strdup(vpi_get_str(vpiFile, callh));
  info.lineno = (int)vpi_get(vpiLineNo, callh);
  info.default_format = get_default_format(name);
  info.scope = scope;
  array_from_iterator(&info, argv);

  /* Because %u and %z may put embedded NULL characters into the
   * returned string strlen() may not match the real size! */
  result = get_display(&size, &info);

  if (fd_mcd > 0) {
    my_mcd_rawwrite(fd_mcd, result, size);
    if ((strncmp(name, "$display", 8) == 0) ||
        (strncmp(name, "$fdisplay", 9) == 0))
      my_mcd_rawwrite(fd_mcd, "\n", 1);
  } else {
    /* Return as a string ($sformatf) */
    val.format = vpiStringVal;
    val.value.str = result;
    vpi_put_value(callh, &val, 0, vpiNoDelay);
  }

  free(result);
  free(info.filename);
  free(info.items);
  return 0;
}

/*
 * The strobe implementation takes the parameter handles that are
 * passed to the calltf and puts them in to an array for safe
 * keeping. That array (and other bookkeeping) is passed, via the
 * struct_cb_info object, to the REadOnlySych function strobe_cb,
 * where it is used to perform the actual formatting and printing.
 */
static PLI_INT32 strobe_cb(p_cb_data cb) {
  struct strobe_cb_info *info = (struct strobe_cb_info *)cb->user_data;

  /* We really need to cancel any $fstrobe() calls for a file when it
   * is closed, but for now we will just skip processing the result.
   * Which has the same basic effect. */
  if ((!IS_MCD(info->fd_mcd) && vpi_get_file(info->fd_mcd) != NULL) ||
      (IS_MCD(info->fd_mcd) && my_mcd_printf(info->fd_mcd, "") != EOF)) {
    char *result;
    unsigned int size;
    /* Because %u and %z may put embedded NULL characters into the
     * returned string strlen() may not match the real size! */
    result = get_display(&size, info);
    my_mcd_rawwrite(info->fd_mcd, result, size);
    my_mcd_rawwrite(info->fd_mcd, "\n", 1);
    free(result);
  }

  free(info->filename);
  free(info->items);
  free(info);
  return 0;
}

/* Check both the $strobe and $fstrobe based tasks. */
static PLI_INT32 sys_strobe_compiletf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  /* These tasks can not have automatic variables and are not monitor. */
  return sys_common_compiletf(name, 1, 0);
}

/* This implements both the $strobe and $fstrobe based tasks. */
static PLI_INT32 sys_strobe_calltf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh, argv, scope;
  struct t_cb_data cb;
  struct t_vpi_time timerec;
  struct strobe_cb_info *info;
  PLI_UINT32 fd_mcd;

  callh = vpi_handle(vpiSysTfCall, 0);
  argv = vpi_iterate(vpiArgument, callh);

  /* Get the file/MC descriptor and verify it is valid. */
  if (name[1] == 'f') {
    errno = 0;
    vpiHandle arg = vpi_scan(argv);
    s_vpi_value val;
    val.format = vpiIntVal;
    vpi_get_value(arg, &val);
    fd_mcd = val.value.integer;

    /* If the MCD is zero we have nothing to do so just return. */
    if (fd_mcd == 0) {
      vpi_free_object(argv);
      return 0;
    }

    if ((!IS_MCD(fd_mcd) && vpi_get_file(fd_mcd) == NULL) ||
        (IS_MCD(fd_mcd) && my_mcd_printf(fd_mcd, "") == EOF)) {
      vpi_printf("WARNING: %s:%d: ", vpi_get_str(vpiFile, callh),
                 (int)vpi_get(vpiLineNo, callh));
      vpi_printf(
          "invalid file descriptor/MCD (0x%x) given "
          "to %s.\n",
          (unsigned int)fd_mcd, name);
      errno = EBADF;
      vpi_free_object(argv);
      return 0;
    }
  } else {
    fd_mcd = 1;
  }

  scope = vpi_handle(vpiScope, callh);
  assert(scope);

  info = calloc(1, sizeof(struct strobe_cb_info));
  info->fd_mcd = fd_mcd;
  /* We could use vpi_get_str(vpiName, callh) to get the task name,
   * but name is already defined. */
  info->name = name;
  info->filename = strdup(vpi_get_str(vpiFile, callh));
  info->lineno = (int)vpi_get(vpiLineNo, callh);
  info->default_format = get_default_format(name);
  info->scope = scope;
  array_from_iterator(info, argv);

  timerec.type = vpiSimTime;
  timerec.low = 0;
  timerec.high = 0;

  cb.reason = cbReadOnlySynch;
  cb.cb_rtn = strobe_cb;
  cb.time = &timerec;
  cb.obj = 0;
  cb.value = 0;
  cb.user_data = (char *)info;
  vpi_register_cb(&cb);
  return 0;
}

/*
 * The $monitor system task works by managing these static variables,
 * and the cbValueChange callbacks associated with registers and
 * nets. Note that it is proper to keep the state in static variables
 * because there can only be one monitor at a time pending (even
 * though that monitor may be watching many variables).
 */

static struct strobe_cb_info monitor_info = {0, 0, 0, 0, 0, 0, 0, 0};
static vpiHandle *monitor_callbacks = 0;
static int monitor_scheduled = 0;
static int monitor_enabled = 1;

static PLI_INT32 monitor_cb_2(p_cb_data cb) {
  char *result;
  unsigned int size;

  (void)cb; /* Parameter is not used. */

  /* Because %u and %z may put embedded NULL characters into the
   * returned string strlen() may not match the real size! */
  result = get_display(&size, &monitor_info);
  my_mcd_rawwrite(monitor_info.fd_mcd, result, size);
  my_mcd_rawwrite(monitor_info.fd_mcd, "\n", 1);
  monitor_scheduled = 0;
  free(result);
  return 0;
}

/*
 * The monitor_cb_1 callback is called when an event occurs somewhere
 * in the simulation. All this function does is schedule the actual
 * display to occur in a ReadOnlySync callback. The monitor_scheduled
 * flag is used to allow only one monitor strobe to be scheduled.
 */
static PLI_INT32 monitor_cb_1(p_cb_data cause) {
  struct t_cb_data cb;
  struct t_vpi_time timerec;

  (void)cause; /* Parameter is not used. */

  if (monitor_enabled == 0) return 0;
  if (monitor_scheduled) return 0;

  /* This this action caused the first trigger, then schedule
     the monitor to happen at the end of the time slice and mark
     it as scheduled. */
  monitor_scheduled += 1;
  timerec.type = vpiSimTime;
  timerec.low = 0;
  timerec.high = 0;

  cb.reason = cbReadOnlySynch;
  cb.cb_rtn = monitor_cb_2;
  cb.time = &timerec;
  cb.obj = 0;
  cb.value = 0;
  vpi_register_cb(&cb);

  return 0;
}

static PLI_INT32 sys_monitor_compiletf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv = vpi_iterate(vpiArgument, callh);

  if (sys_check_args(callh, argv, name, 1, 1)) vpi_control(vpiFinish, 1);
  return 0;
}

static PLI_INT32 sys_monitor_calltf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh, argv, scope;
  unsigned idx;
  struct t_cb_data cb;
  struct t_vpi_time timerec;

  (void)name; /* Parameter is not used. */

  callh = vpi_handle(vpiSysTfCall, 0);
  argv = vpi_iterate(vpiArgument, callh);

  /* If there was a previous $monitor, then remove the callbacks
     related to it. */
  if (monitor_callbacks) {
    for (idx = 0; idx < monitor_info.nitems; idx += 1)
      if (monitor_callbacks[idx]) vpi_remove_cb(monitor_callbacks[idx]);

    free(monitor_callbacks);
    monitor_callbacks = 0;

    free(monitor_info.filename);
    free(monitor_info.items);
    monitor_info.items = 0;
    monitor_info.nitems = 0;
    monitor_info.name = 0;
  }

  scope = vpi_handle(vpiScope, callh);
  assert(scope);
  /* Make an array of handles from the argument list. */
  array_from_iterator(&monitor_info, argv);
  monitor_info.name = name;
  monitor_info.filename = strdup(vpi_get_str(vpiFile, callh));
  monitor_info.lineno = (int)vpi_get(vpiLineNo, callh);
  monitor_info.default_format = get_default_format(name);
  monitor_info.scope = scope;
  monitor_info.fd_mcd = 1;

  /* Attach callbacks to all the parameters that might change. */
  monitor_callbacks = calloc(monitor_info.nitems, sizeof(vpiHandle));

  timerec.type = vpiSuppressTime;
  cb.reason = cbValueChange;
  cb.cb_rtn = monitor_cb_1;
  cb.time = &timerec;
  cb.value = NULL;
  for (idx = 0; idx < monitor_info.nitems; idx += 1) {
    switch (vpi_get(vpiType, monitor_info.items[idx])) {
      case vpiMemoryWord:
        /*
         * We only support constant selections. Make this
         * better when we add a real compiletf routine.
         */
        assert(vpi_get(vpiConstantSelect, monitor_info.items[idx]));
      case vpiNet:
      case vpiReg:
      case vpiIntegerVar:
      case vpiBitVar:
      case vpiByteVar:
      case vpiShortIntVar:
      case vpiIntVar:
      case vpiLongIntVar:
      case vpiRealVar:
      case vpiPartSelect:
        /* Monitoring reg and net values involves setting
           a callback for value changes. Pass the storage
           pointer for the callback itself as user_data so
           that the callback can refresh itself. */
        cb.user_data = (char *)(monitor_callbacks + idx);
        cb.obj = monitor_info.items[idx];
        monitor_callbacks[idx] = vpi_register_cb(&cb);
        break;
    }
  }

  /* When the $monitor is called, it schedules a first display
     for the end of the current time, like a $strobe. */
  monitor_cb_1(0);

  return 0;
}

static PLI_INT32 sys_monitoron_calltf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  (void)name; /* Parameter is not used. */
  monitor_enabled = 1;
  monitor_cb_1(0);
  return 0;
}

static PLI_INT32 sys_monitoroff_calltf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  (void)name; /* Parameter is not used. */
  monitor_enabled = 0;
  return 0;
}

static PLI_INT32 sys_swrite_compiletf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv = vpi_iterate(vpiArgument, callh);
  vpiHandle arg;
  PLI_INT32 type;

  /* Check that there are arguments. */
  if (argv == 0) {
    vpi_printf("ERROR:%s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s requires at least one argument.\n", name);
    vpi_control(vpiFinish, 1);
    return 0;
  }

  /* The first argument must be a register or a SV string. */
  arg = vpi_scan(argv); /* This should never be zero. */
  type = vpi_get(vpiType, arg);
  if (type != vpiReg && type != vpiStringVar) {
    vpi_printf("ERROR:%s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s's first argument must be a register or SV string.\n", name);
    vpi_control(vpiFinish, 1);
    return 0;
  }

  if (sys_check_args(callh, argv, name, 0, 0)) vpi_control(vpiFinish, 1);
  return 0;
}

static PLI_INT32 sys_swrite_calltf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh, argv, reg, scope;
  struct strobe_cb_info info;
  s_vpi_value val;
  unsigned int size;

  callh = vpi_handle(vpiSysTfCall, 0);
  argv = vpi_iterate(vpiArgument, callh);
  reg = vpi_scan(argv);

  scope = vpi_handle(vpiScope, callh);
  assert(scope);
  /* We could use vpi_get_str(vpiName, callh) to get the task name, but
   * name is already defined. */
  info.name = name;
  info.filename = strdup(vpi_get_str(vpiFile, callh));
  info.lineno = (int)vpi_get(vpiLineNo, callh);
  info.default_format = get_default_format(name);
  info.scope = scope;
  array_from_iterator(&info, argv);

  /* Because %u and %z may put embedded NULL characters into the returned
   * string strlen() may not match the real size! */
  val.value.str = get_display(&size, &info);
  val.format = vpiStringVal;
  vpi_put_value(reg, &val, 0, vpiNoDelay);
  if (size != strlen(val.value.str)) {
    vpi_printf(
        "WARNING: %s:%d: %s returned a value with an embedded NULL "
        "(see %%u/%%z).\n",
        info.filename, info.lineno, name);
  }

  free(val.value.str);
  free(info.filename);
  free(info.items);
  return 0;
}

static PLI_INT32 sys_sformat_compiletf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv = vpi_iterate(vpiArgument, callh);
  vpiHandle arg;
  PLI_INT32 type;

  /* Check that there are arguments. */
  if (argv == 0) {
    vpi_printf("ERROR:%s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s requires at least two arguments.\n", name);
    vpi_control(vpiFinish, 1);
    return 0;
  }

  /* The first argument must be a register or a SV string. */
  arg = vpi_scan(argv); /* This should never be zero. */
  type = vpi_get(vpiType, arg);
  if (type != vpiReg && type != vpiStringVar) {
    vpi_printf("ERROR:%s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s's first argument must be a register or SV string.\n", name);
    vpi_control(vpiFinish, 1);
    return 0;
  }

  /* The second argument must be a string, a register or a SV string. */
  arg = vpi_scan(argv);
  if (arg == 0) {
    vpi_printf("ERROR:%s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s requires at least two arguments.\n", name);
    vpi_control(vpiFinish, 1);
    return 0;
  }
  type = vpi_get(vpiType, arg);
  if (((type != vpiConstant && type != vpiParameter) ||
       vpi_get(vpiConstType, arg) != vpiStringConst) &&
      type != vpiReg && type != vpiStringVar) {
    vpi_printf("ERROR:%s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s's second argument must be a string or a register.\n", name);
    vpi_control(vpiFinish, 1);
    return 0;
  }

  if (sys_check_args(callh, argv, name, 0, 0)) vpi_control(vpiFinish, 1);
  return 0;
}

static PLI_INT32 sys_sformat_calltf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh, argv, reg, scope;
  struct strobe_cb_info info;
  s_vpi_value val;
  char *result, *fmt;
  unsigned int idx, size;

  callh = vpi_handle(vpiSysTfCall, 0);
  argv = vpi_iterate(vpiArgument, callh);
  reg = vpi_scan(argv);
  val.format = vpiStringVal;
  vpi_get_value(vpi_scan(argv), &val);
  fmt = strdup(val.value.str);

  scope = vpi_handle(vpiScope, callh);
  assert(scope);
  /* We could use vpi_get_str(vpiName, callh) to get the task name, but
   * name is already defined. */
  info.name = name;
  info.filename = strdup(vpi_get_str(vpiFile, callh));
  info.lineno = (int)vpi_get(vpiLineNo, callh);
  info.default_format = get_default_format(name);
  info.scope = scope;
  array_from_iterator(&info, argv);
  idx = -1;
  size = get_format(&result, fmt, &info, &idx);
  free(fmt);

  if (idx + 1 < info.nitems) {
    vpi_printf("WARNING: %s:%d: %s has %d extra argument(s).\n", info.filename,
               info.lineno, name, info.nitems - idx - 1);
  }

  val.value.str = result;
  val.format = vpiStringVal;
  vpi_put_value(reg, &val, 0, vpiNoDelay);
  if (size != strlen(val.value.str)) {
    vpi_printf(
        "WARNING: %s:%d: %s returned a value with an embedded NULL "
        "(see %%u/%%z).\n",
        info.filename, info.lineno, name);
  }

  free(val.value.str);
  free(info.filename);
  free(info.items);
  return 0;
}

static PLI_INT32 sys_sformatf_compiletf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv = vpi_iterate(vpiArgument, callh);
  vpiHandle arg;
  PLI_INT32 type;

  /* Check that there are arguments. */
  if (argv == 0) {
    vpi_printf("ERROR:%s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s requires at least one argument.\n", name);
    vpi_control(vpiFinish, 1);
    return 0;
  }

  /* The first argument must be a string, a register or a SV string. */
  arg = vpi_scan(argv);
  if (arg == 0) {
    vpi_printf("ERROR:%s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s requires at least one argument.\n", name);
    vpi_control(vpiFinish, 1);
    return 0;
  }
  type = vpi_get(vpiType, arg);
  if (((type != vpiConstant && type != vpiParameter) ||
       vpi_get(vpiConstType, arg) != vpiStringConst) &&
      type != vpiReg && type != vpiStringVar) {
    vpi_printf("ERROR:%s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s's first argument must be a string or a register.\n", name);
    vpi_control(vpiFinish, 1);
    return 0;
  }

  if (sys_check_args(callh, argv, name, 0, 0)) vpi_control(vpiFinish, 1);
  return 0;
}

static PLI_INT32 sys_end_of_compile(p_cb_data cb_data) {
  (void)cb_data; /* Parameter is not used. */
                 /* The default timeformat prints times in unit of simulation
                    precision. */
  free(timeformat_info.suff);
  timeformat_info.suff = strdup("");
  timeformat_info.units = vpi_get(vpiTimePrecision, 0);
  timeformat_info.prec = 0;
  timeformat_info.width = 20;
  return 0;
}

static PLI_INT32 sys_timeformat_compiletf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv = vpi_iterate(vpiArgument, callh);

  if (argv) {
    vpiHandle arg;

    /* Check that the unit argument is numeric. */
    if (!is_numeric_obj(vpi_scan(argv))) {
      vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
                 (int)vpi_get(vpiLineNo, callh));
      vpi_printf("%s's units argument must be numeric.\n", name);
      vpi_control(vpiFinish, 1);
    }

    /* Check that the precision argument is given and is numeric. */
    arg = vpi_scan(argv);
    if (!arg) {
      vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
                 (int)vpi_get(vpiLineNo, callh));
      vpi_printf("%s requires zero or four arguments.\n", name);
      vpi_control(vpiFinish, 1);
      return 0;
    }

    if (!is_numeric_obj(arg)) {
      vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
                 (int)vpi_get(vpiLineNo, callh));
      vpi_printf("%s's precision argument must be numeric.\n", name);
      vpi_control(vpiFinish, 1);
    }

    /* Check that the suffix argument is given and is a string. */
    arg = vpi_scan(argv);
    if (!arg) {
      vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
                 (int)vpi_get(vpiLineNo, callh));
      vpi_printf("%s requires zero or four arguments.\n", name);
      vpi_control(vpiFinish, 1);
      return 0;
    }

    if (!is_string_obj(arg)) {
      vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
                 (int)vpi_get(vpiLineNo, callh));
      vpi_printf("%s's suffix argument must be a string.\n", name);
      vpi_control(vpiFinish, 1);
    }

    /* Check that the min. width argument is given and is numeric. */
    arg = vpi_scan(argv);
    if (!arg) {
      vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
                 (int)vpi_get(vpiLineNo, callh));
      vpi_printf("%s requires zero or four arguments.\n", name);
      vpi_control(vpiFinish, 1);
      return 0;
    }

    if (!is_numeric_obj(arg)) {
      vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
                 (int)vpi_get(vpiLineNo, callh));
      vpi_printf("%s's minimum width argument must be numeric.\n", name);
      vpi_control(vpiFinish, 1);
    }

    /* Make sure there are no extra arguments. */
    check_for_extra_args(argv, callh, name, "four arguments", 0);
  }

  return 0;
}

static PLI_INT32 sys_timeformat_calltf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  s_vpi_value value;
  vpiHandle sys = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv = vpi_iterate(vpiArgument, sys);

  (void)name; /* Parameter is not used. */

  if (argv) {
    vpiHandle units = vpi_scan(argv);
    vpiHandle prec = vpi_scan(argv);
    vpiHandle suff = vpi_scan(argv);
    vpiHandle wid = vpi_scan(argv);

    vpi_free_object(argv);

    value.format = vpiIntVal;
    vpi_get_value(units, &value);
    timeformat_info.units = value.value.integer;

    value.format = vpiIntVal;
    vpi_get_value(prec, &value);
    timeformat_info.prec = value.value.integer;

    value.format = vpiStringVal;
    vpi_get_value(suff, &value);
    free(timeformat_info.suff);
    timeformat_info.suff = strdup(value.value.str);

    value.format = vpiIntVal;
    vpi_get_value(wid, &value);
    timeformat_info.width = value.value.integer;
  } else {
    /* If no arguments are given then use the default values. */
    sys_end_of_compile(NULL);
  }

  return 0;
}

static const char *pts_convert(int value) {
  const char *string;
  switch (value) {
    case 2:
      string = "100s";
      break;
    case 1:
      string = "10s";
      break;
    case 0:
      string = "1s";
      break;
    case -1:
      string = "100ms";
      break;
    case -2:
      string = "10ms";
      break;
    case -3:
      string = "1ms";
      break;
    case -4:
      string = "100us";
      break;
    case -5:
      string = "10us";
      break;
    case -6:
      string = "1us";
      break;
    case -7:
      string = "100ns";
      break;
    case -8:
      string = "10ns";
      break;
    case -9:
      string = "1ns";
      break;
    case -10:
      string = "100ps";
      break;
    case -11:
      string = "10ps";
      break;
    case -12:
      string = "1ps";
      break;
    case -13:
      string = "100fs";
      break;
    case -14:
      string = "10fs";
      break;
    case -15:
      string = "1fs";
      break;
    default:
      string = "invalid";
      assert(0);
  }
  return string;
}

static PLI_INT32 sys_printtimescale_compiletf(
    ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv = vpi_iterate(vpiArgument, callh);

  if (argv) {
    vpiHandle arg = vpi_scan(argv);
    switch (vpi_get(vpiType, arg)) {
      case vpiFunction:
      case vpiGenScope:
      case vpiIntegerVar:
      case vpiBitVar:
      case vpiByteVar:
      case vpiShortIntVar:
      case vpiIntVar:
      case vpiLongIntVar:
      case vpiMemory:
      case vpiMemoryWord:
      case vpiModule:
      case vpiNamedBegin:
      case vpiNamedEvent:
      case vpiNamedFork:
      case vpiNet:
      case vpiNetArray:
        //		case vpiNetBit: // Unused and unavailable in Icarus
      case vpiParameter:
      case vpiPartSelect:
      case vpiRealVar:
      case vpiReg:
        //		case vpiRegBit: // Unused and unavailable in Icarus
      case vpiTask:
      case vpiTimeVar:  // Unused in Icarus
        break;

      default:
        vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
                   (int)vpi_get(vpiLineNo, callh));
        vpi_printf("%s's argument must have a module, given a %s.\n", name,
                   vpi_get_str(vpiType, arg));
        vpi_control(vpiFinish, 1);
    }

    /* Make sure there are no extra arguments. */
    check_for_extra_args(argv, callh, name, "one argument", 1);
  }

  return 0;
}

static PLI_INT32 sys_printtimescale_calltf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv = vpi_iterate(vpiArgument, callh);
  vpiHandle item, scope;

  (void)name; /* Parameter is not used. */

  if (!argv) {
    item = sys_func_module(callh);
  } else {
    item = vpi_scan(argv);
    vpi_free_object(argv);
  }
  scope = sys_func_module(item);

  vpi_printf("Time scale of (%s) is ", vpi_get_str(vpiFullName, item));
  vpi_printf("%s / ", pts_convert(vpi_get(vpiTimeUnit, scope)));
  vpi_printf("%s\n", pts_convert(vpi_get(vpiTimePrecision, scope)));

  return 0;
}

static PLI_INT32 sys_fatal_compiletf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv = vpi_iterate(vpiArgument, callh);

  if (argv) {
    vpiHandle arg = vpi_scan(argv);

    /* Check that finish_number (1st argument) is numeric */
    if (!is_numeric_obj(arg)) {
      vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
                 (int)vpi_get(vpiLineNo, callh));
      vpi_printf("%s's finish number must be numeric.\n", name);
      vpi_control(vpiFinish, 1);
    }

    if (sys_check_args(callh, argv, name, 0, 0)) {
      vpi_control(vpiFinish, 1);
    }
  }

  return 0;
}

static PLI_INT32 sys_severity_calltf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv = vpi_iterate(vpiArgument, callh);
  vpiHandle scope;
  struct strobe_cb_info info;
  struct t_vpi_time now;
  PLI_UINT64 now64;
  char *sstr, *t, *dstr;
  unsigned int size, location = 0;
  s_vpi_value finish_number;

  /* Set the default finish number for $fatal. */
  finish_number.value.integer = 1;

  /* Check that the finish number is in range. */
  if (strncmp(name, "$fatal", 6) == 0 && argv) {
    vpiHandle arg = vpi_scan(argv);
    finish_number.format = vpiIntVal;
    vpi_get_value(arg, &finish_number);
    if ((finish_number.value.integer < 0) ||
        (finish_number.value.integer > 2)) {
      vpi_printf("WARNING: %s:%d: ", vpi_get_str(vpiFile, callh),
                 (int)vpi_get(vpiLineNo, callh));
      vpi_printf(
          "$fatal called with finish_number of %d, "
          "but it must be 0, 1, or 2.\n",
          (int)finish_number.value.integer);
      finish_number.value.integer = 1;
    }
  }

  /* convert name to upper and drop $ to get severity string */
  sstr = strdup(name) + 1;
  for (t = sstr; *t; t += 1) *t = toupper((int)*t);

  scope = vpi_handle(vpiScope, callh);
  assert(scope);
  info.name = name;
  info.filename = strdup(vpi_get_str(vpiFile, callh));
  info.lineno = (int)vpi_get(vpiLineNo, callh);
  info.default_format = vpiDecStrVal;
  info.scope = scope;
  array_from_iterator(&info, argv);

  vpi_printf("%s: %s:%d: ", sstr, info.filename, info.lineno);

  dstr = get_display(&size, &info);
  while (location < size) {
    if (dstr[location] == '\0') {
      my_mcd_printf(1, "%c", '\0');
      location += 1;
    } else {
      my_mcd_printf(1, "%s", &dstr[location]);
      location += strlen(&dstr[location]);
    }
  }

  now.type = vpiSimTime;
  vpi_get_time(0, &now);
  now64 = timerec_to_time64(&now);

  vpi_printf("\n%*s  Time: %" PLI_UINT64_FMT " Scope: %s\n", (int)strlen(sstr),
             " ", now64, vpi_get_str(vpiFullName, scope));

  free(--sstr); /* Get the $ back. */
  free(info.filename);
  free(info.items);
  free(dstr);

  if (strncmp(name, "$fatal", 6) == 0) {
    /* Set the exit code from vvp as an error code. */
    vpip_set_return_value(1);
    /* Now tell the simulator to finish. */
    vpi_control(vpiFinish, finish_number.value.integer);
  }

  return 0;
}

static PLI_INT32 sys_end_of_simulation(p_cb_data cb_data) {
  (void)cb_data; /* Parameter is not used. */
  free(monitor_callbacks);
  monitor_callbacks = 0;
  free(monitor_info.filename);
  free(monitor_info.items);
  monitor_info.items = 0;
  monitor_info.nitems = 0;
  monitor_info.name = 0;

  free(timeformat_info.suff);
  timeformat_info.suff = 0;
  return 0;
}

void sys_display_register(void) {
  s_cb_data cb_data;
  s_vpi_systf_data tf_data;
  vpiHandle res;

  check_command_line_args();

  /*============================== display */
  tf_data.type = vpiSysTask;
  tf_data.tfname = "$display";
  tf_data.calltf = sys_display_calltf;
  tf_data.compiletf = sys_display_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$display";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysTask;
  tf_data.tfname = "$displayh";
  tf_data.calltf = sys_display_calltf;
  tf_data.compiletf = sys_display_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$displayh";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysTask;
  tf_data.tfname = "$displayo";
  tf_data.calltf = sys_display_calltf;
  tf_data.compiletf = sys_display_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$displayo";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysTask;
  tf_data.tfname = "$displayb";
  tf_data.calltf = sys_display_calltf;
  tf_data.compiletf = sys_display_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$displayb";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  /*============================== write */
  tf_data.type = vpiSysTask;
  tf_data.tfname = "$write";
  tf_data.calltf = sys_display_calltf;
  tf_data.compiletf = sys_display_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$write";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysTask;
  tf_data.tfname = "$writeh";
  tf_data.calltf = sys_display_calltf;
  tf_data.compiletf = sys_display_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$writeh";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysTask;
  tf_data.tfname = "$writeo";
  tf_data.calltf = sys_display_calltf;
  tf_data.compiletf = sys_display_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$writeo";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysTask;
  tf_data.tfname = "$writeb";
  tf_data.calltf = sys_display_calltf;
  tf_data.compiletf = sys_display_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$writeb";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  /*============================== strobe */
  tf_data.type = vpiSysTask;
  tf_data.tfname = "$strobe";
  tf_data.calltf = sys_strobe_calltf;
  tf_data.compiletf = sys_strobe_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$strobe";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysTask;
  tf_data.tfname = "$strobeh";
  tf_data.calltf = sys_strobe_calltf;
  tf_data.compiletf = sys_strobe_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$strobeh";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysTask;
  tf_data.tfname = "$strobeo";
  tf_data.calltf = sys_strobe_calltf;
  tf_data.compiletf = sys_strobe_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$strobeo";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysTask;
  tf_data.tfname = "$strobeb";
  tf_data.calltf = sys_strobe_calltf;
  tf_data.compiletf = sys_strobe_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$strobeb";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  /*============================== fstrobe */
  tf_data.type = vpiSysTask;
  tf_data.tfname = "$fstrobe";
  tf_data.calltf = sys_strobe_calltf;
  tf_data.compiletf = sys_strobe_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$fstrobe";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysTask;
  tf_data.tfname = "$fstrobeh";
  tf_data.calltf = sys_strobe_calltf;
  tf_data.compiletf = sys_strobe_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$fstrobeh";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysTask;
  tf_data.tfname = "$fstrobeo";
  tf_data.calltf = sys_strobe_calltf;
  tf_data.compiletf = sys_strobe_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$fstrobeo";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysTask;
  tf_data.tfname = "$fstrobeb";
  tf_data.calltf = sys_strobe_calltf;
  tf_data.compiletf = sys_strobe_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$fstrobeb";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  /*============================== monitor */
  tf_data.type = vpiSysTask;
  tf_data.tfname = "$monitor";
  tf_data.calltf = sys_monitor_calltf;
  tf_data.compiletf = sys_monitor_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$monitor";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysTask;
  tf_data.tfname = "$monitorh";
  tf_data.calltf = sys_monitor_calltf;
  tf_data.compiletf = sys_monitor_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$monitorh";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysTask;
  tf_data.tfname = "$monitoro";
  tf_data.calltf = sys_monitor_calltf;
  tf_data.compiletf = sys_monitor_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$monitoro";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysTask;
  tf_data.tfname = "$monitorb";
  tf_data.calltf = sys_monitor_calltf;
  tf_data.compiletf = sys_monitor_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$monitorb";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysTask;
  tf_data.tfname = "$monitoron";
  tf_data.calltf = sys_monitoron_calltf;
  tf_data.compiletf = sys_no_arg_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$monitoron";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysTask;
  tf_data.tfname = "$monitoroff";
  tf_data.calltf = sys_monitoroff_calltf;
  tf_data.compiletf = sys_no_arg_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$monitoroff";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  /*============================== fdisplay */
  tf_data.type = vpiSysTask;
  tf_data.tfname = "$fdisplay";
  tf_data.calltf = sys_display_calltf;
  tf_data.compiletf = sys_display_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$fdisplay";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysTask;
  tf_data.tfname = "$fdisplayh";
  tf_data.calltf = sys_display_calltf;
  tf_data.compiletf = sys_display_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$fdisplayh";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysTask;
  tf_data.tfname = "$fdisplayo";
  tf_data.calltf = sys_display_calltf;
  tf_data.compiletf = sys_display_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$fdisplayo";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysTask;
  tf_data.tfname = "$fdisplayb";
  tf_data.calltf = sys_display_calltf;
  tf_data.compiletf = sys_display_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$fdisplayb";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  /*============================== fwrite */
  tf_data.type = vpiSysTask;
  tf_data.tfname = "$fwrite";
  tf_data.calltf = sys_display_calltf;
  tf_data.compiletf = sys_display_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$fwrite";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysTask;
  tf_data.tfname = "$fwriteh";
  tf_data.calltf = sys_display_calltf;
  tf_data.compiletf = sys_display_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$fwriteh";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysTask;
  tf_data.tfname = "$fwriteo";
  tf_data.calltf = sys_display_calltf;
  tf_data.compiletf = sys_display_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$fwriteo";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysTask;
  tf_data.tfname = "$fwriteb";
  tf_data.calltf = sys_display_calltf;
  tf_data.compiletf = sys_display_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$fwriteb";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  /*============================== swrite */
  tf_data.type = vpiSysTask;
  tf_data.tfname = "$swrite";
  tf_data.calltf = sys_swrite_calltf;
  tf_data.compiletf = sys_swrite_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$swrite";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysTask;
  tf_data.tfname = "$swriteh";
  tf_data.calltf = sys_swrite_calltf;
  tf_data.compiletf = sys_swrite_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$swriteh";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysTask;
  tf_data.tfname = "$swriteo";
  tf_data.calltf = sys_swrite_calltf;
  tf_data.compiletf = sys_swrite_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$swriteo";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysTask;
  tf_data.tfname = "$swriteb";
  tf_data.calltf = sys_swrite_calltf;
  tf_data.compiletf = sys_swrite_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$swriteb";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysTask;
  tf_data.tfname = "$sformat";
  tf_data.calltf = sys_sformat_calltf;
  tf_data.compiletf = sys_sformat_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$sformat";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysFunc;
  tf_data.sysfunctype = vpiStringFunc;
  tf_data.tfname = "$sformatf";
  tf_data.calltf = sys_display_calltf;
  tf_data.compiletf = sys_sformatf_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$sformatf";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  /*============================ timeformat */
  tf_data.type = vpiSysTask;
  tf_data.tfname = "$timeformat";
  tf_data.calltf = sys_timeformat_calltf;
  tf_data.compiletf = sys_timeformat_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$timeformat";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysTask;
  tf_data.tfname = "$printtimescale";
  tf_data.calltf = sys_printtimescale_calltf;
  tf_data.compiletf = sys_printtimescale_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$printtimescale";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  /*============================ severity tasks */
  tf_data.type = vpiSysTask;
  tf_data.tfname = "$fatal";
  tf_data.calltf = sys_severity_calltf;
  tf_data.compiletf = sys_fatal_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$fatal";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysTask;
  tf_data.tfname = "$error";
  tf_data.calltf = sys_severity_calltf;
  tf_data.compiletf = sys_display_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$error";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysTask;
  tf_data.tfname = "$warning";
  tf_data.calltf = sys_severity_calltf;
  tf_data.compiletf = sys_display_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$warning";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysTask;
  tf_data.tfname = "$info";
  tf_data.calltf = sys_severity_calltf;
  tf_data.compiletf = sys_display_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$info";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  cb_data.reason = cbEndOfCompile;
  cb_data.time = 0;
  cb_data.cb_rtn = sys_end_of_compile;
  cb_data.user_data = "system";
  vpi_register_cb(&cb_data);

  cb_data.reason = cbEndOfSimulation;
  cb_data.cb_rtn = sys_end_of_simulation;
  cb_data.user_data = "system";
  vpi_register_cb(&cb_data);
}
